package com.leyou.search.service;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.exception.pojo.ExceptionEnum;
import com.leyou.common.exception.pojo.LyException;
import com.leyou.common.pojo.PageResult;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.SpuDTO;
import com.leyou.item.entity.*;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.entity.Goods;
import com.leyou.search.repository.SearchRepository;
import com.leyou.search.utils.HighlightUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class SearchService {

    @Autowired
    private SearchRepository searchRepository;

    @Autowired
    private ElasticsearchTemplate esTemplate;

    @Autowired
    private ItemClient itemClient;

    /**
     * 将数据库中的商品转成索引库中文档的业务方法
     */
    public Goods buildGoods(SpuDTO spuDTO) {
        //根据spuId查询Sku集合
        List<Sku> skus = itemClient.findSkusBySpuId(spuDTO.getId());

        //创建一个map的集合封装所需要的sku字段
        List<Map<String, Object>> skuMaps = new ArrayList<>();
        //遍历skus
        skus.forEach(sku -> {
            Map<String, Object> skuMap = new HashMap<>();
            skuMap.put("id", sku.getId());
            skuMap.put("image", StringUtils.substringBefore(sku.getImages(), ","));
            skuMap.put("price", sku.getPrice());
            skuMap.put("title", sku.getTitle().substring(spuDTO.getName().length()));
            skuMaps.add(skuMap);
        });

        if (skuMaps != null && skuMaps.size() > 1) {
            Collections.sort(skuMaps, new Comparator<Map<String, Object>>() {
                @Override
                public int compare(Map<String, Object> o1, Map<String, Object> o2) {
                    Integer o1Value = Integer.valueOf(o1.get("price").toString());
                    Integer o2Value = Integer.valueOf(o2.get("price").toString());
                    return o1Value.compareTo(o2Value);
                }
            });
        }

        //从sku集合中收集到所有价格
        Set<Long> prices = skus.stream().map(Sku::getPrice).collect(Collectors.toSet());

        //给all字段赋值
        String all = spuDTO.getBrandName()
                + spuDTO.getCategoryName()
                + skus.stream().map(Sku::getTitle).collect(Collectors.joining());

        //定义存放规格参数过滤条件的map
        Map<String, Object> specs = new HashMap<>();
        //根据分类id查询规格参数列表
        List<SpecParam> specParams = itemClient.findSpecParams(null, spuDTO.getCid3(), true);
        //根据SpuId查询SpuDetail对象
        SpuDetail spuDetail = itemClient.findSpuDetailById(spuDTO.getId());
        //获取通用规格参数所在的字段
        String genericSpecStr = spuDetail.getGenericSpec();
        //将字符串格式的通用规格参数转成map格式
        Map<Long, Object> genericSpecObj = JsonUtils.toMap(genericSpecStr, Long.class, Object.class);
        //获取特有规格参数所在的字段
        String specialSpecStr = spuDetail.getSpecialSpec();
        //将字符串格式的特有规格参数转成map格式
        Map<Long, List<Object>> specialSpecObj = JsonUtils.nativeRead(specialSpecStr, new TypeReference<Map<Long, List<Object>>>() {
        });
        //遍历规格参数过滤条件map的key值所在的对象的集合
        specParams.forEach(specParam -> {
            String key = specParam.getName();
            Object value = null;
            //判断当前规格参值的来源
            if (specParam.getGeneric()) {
                value = genericSpecObj.get(specParam.getId());
            } else {
                value = specialSpecObj.get(specParam.getId());
            }
            //判断当前value是否是数字
            if (specParam.getNumeric()) {
                value = chooseSegment(value, specParam);
            }
            specs.put(key, value);
        });

        //创建返回值对象
        Goods goods = new Goods();
        goods.setId(spuDTO.getId());
        goods.setSpuName(spuDTO.getName());
        goods.setSubTitle(spuDTO.getSubTitle());
        goods.setBrandId(spuDTO.getBrandId());
        goods.setCategoryId(spuDTO.getCid3());
        goods.setCreateTime(spuDTO.getCreateTime().getTime());
        goods.setSkus(JsonUtils.toString(skuMaps));
        goods.setPrice(prices);
        goods.setAll(all);
        goods.setSpecs(specs);

        return goods;
    }

    private String chooseSegment(Object value, SpecParam p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }
    //--------------------------------------------------------------------------------------------------------

    /**
     * 提供渲染详情页的数据service
     *
     * @param request
     * @return
     */
    public Map<String, Object> loadSearchPage(SearchRequest request) {
        //根据条件分页查询商品信息
        PageResult<GoodsDTO> pageResult = itemPageQuery(request);

        //根据条件聚合查询出所有过滤条件
        Map<String, List<?>> filterConditions = aggQuery(request);

        Map<String, Object> result = new HashMap<>();
        result.put("filterConditions", filterConditions);
        result.put("itemsList", pageResult.getItems());
        result.put("totalCount", pageResult.getTotal());
        result.put("totalPages", pageResult.getTotalPage());
        return result;
    }

    //根据条件分页查询商品信息
    public PageResult<GoodsDTO> itemPageQuery(SearchRequest request) {
        //提供封装复杂条件查询的查询对象
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        //指定要查询的域字段
        nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "spuName", "subTitle", "skus", "price", "createTime"}, null));

        //指定查询分页信息
        nativeSearchQueryBuilder.withPageable(PageRequest.of(request.getPage() - 1, request.getSize()));

        if (request.getType() == 1 && request.getType() != null) {
            nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.ASC));
        } else if (request.getType() == 2 && request.getType() != null) {
            nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC));
        }

        //指定查询条件
        nativeSearchQueryBuilder.withQuery(createQueryBuilder(request));
        //指定高亮字段
        HighlightUtils.highlightField(nativeSearchQueryBuilder, "spuName");
        //执行分页查询
        AggregatedPage<Goods> goodsPageResult = esTemplate.queryForPage(
                nativeSearchQueryBuilder.build(),
                Goods.class,
                HighlightUtils.highlightBody(Goods.class, "spuName"));

        //获取数据列表
        List<Goods> goodsList = goodsPageResult.getContent();

        if (CollectionUtils.isEmpty(goodsList)) {
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        //将Goods集合转成GoodsDTO集合
        List<GoodsDTO> goodsDTOS = BeanHelper.copyWithCollection(goodsList, GoodsDTO.class);
        //封装自定义的分页对象
        PageResult<GoodsDTO> pageResult = new PageResult<>(
                goodsPageResult.getTotalElements(),
                Long.valueOf(goodsPageResult.getTotalPages()),
                goodsDTOS
        );
        return pageResult;
    }


    //根据条件聚合查询出所有过滤条件
    public Map<String, List<?>> aggQuery(SearchRequest request) {
        Map<String, List<?>> resultMap = new LinkedHashMap<>();
        //提供一个封装复杂条件的对象
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        //指定要查询的域字段 [可以不要]
        nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
        //指定查询分页信息 [可以不要]
        nativeSearchQueryBuilder.withPageable(PageRequest.of(0, 1));
        //指定查询条件
        nativeSearchQueryBuilder.withQuery(createQueryBuilder(request));
        //指定分类的聚合名称
        String aggCategoryName = "categoryAgg";
        //添加分类的聚合条件
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(aggCategoryName).field("categoryId"));
        //指定品牌的聚合名称
        String aggBrandName = "brandAgg";
        //添加品牌的聚合结果
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(aggBrandName).field("brandId"));

        //执行聚合查询
        AggregatedPage<Goods> aggResult = esTemplate.queryForPage(nativeSearchQueryBuilder.build(), Goods.class);

        //获取所有聚合结果
        Aggregations aggregations = aggResult.getAggregations();
        //通过分类聚合名称获取分类聚合结果
        Terms categoryTerm = aggregations.get(aggCategoryName);
        //解析分类聚合结果并放入结果map中
        List<Long> cids = handlerCategoryAggToMap(resultMap, categoryTerm);
        //通过品牌聚合名称获取品牌聚合结果
        Terms brandTerm = aggregations.get(aggBrandName);
        //解析品牌聚合结果并放入结果map中
        handlerBrandAggToMap(resultMap, brandTerm);
        //通过分类id的集合封装动态过滤条件
        handerActiveSpecParams(resultMap, cids, createQueryBuilder(request));
        return resultMap;
    }

    //封装动态规格过滤条件的聚合结果
    private void handerActiveSpecParams(Map<String, List<?>> resultMap, List<Long> cids, QueryBuilder queryBuilder) {
        cids.forEach(cid -> {
            //提供一个封装复杂条件的对象
            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
            //指定要查询的域字段 [可以不要]
            nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
            //指定查询分页信息 [可以不要]
            nativeSearchQueryBuilder.withPageable(PageRequest.of(0, 1));
            //指定查询条件
            nativeSearchQueryBuilder.withQuery(queryBuilder);

            //根据分类id查询动态过滤条件规格参数
            List<SpecParam> specParams = itemClient.findSpecParams(null, cid, true);
            //循环
            specParams.forEach(specParam -> {
                //指定规格参数的聚合名称
                String specName = specParam.getName();
                //指定规格参数的域
                String specField = "specs." + specName + ".keyword";
                //添加分类的聚合条件
                nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(specName).field(specField));
            });
            //执行聚合查询
            AggregatedPage<Goods> aggResult = esTemplate.queryForPage(nativeSearchQueryBuilder.build(), Goods.class);
            //获取所有聚合结果
            Aggregations aggregations = aggResult.getAggregations();
            //循环
            specParams.forEach(specParam -> {
                //指定规格参数的聚合名称
                String specName = specParam.getName();
                //通过当前规格参数的聚合结果名称获取聚合结果
                Terms specTerm = aggregations.get(specName);
                //解析聚合结果得到最终的过滤条件集合
                List<String> specAggList = specTerm.getBuckets()
                        .stream()
                        .map(Terms.Bucket::getKeyAsString)
                        .collect(Collectors.toList());
                //放入结果map中
                resultMap.put(specName, specAggList);
            });
        });
    }

    //封装品牌聚合结果
    private void handlerBrandAggToMap(Map<String, List<?>> resultMap, Terms brandTerm) {
        //获取聚合结果中品牌的id的集合
        List<Long> brandIds = brandTerm.getBuckets()
                .stream()
                .map(Terms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        //根据品牌id的集合查询品牌对象的集合
        List<Brand> brands = itemClient.findBrandsByIds(brandIds);
        //放入结果map
        resultMap.put("品牌", brands);
    }

    //封装分类聚合结果
    private List<Long> handlerCategoryAggToMap(Map<String, List<?>> resultMap, Terms categoryTerm) {
        //从聚合结果中获取到分类的id的集合
        List<Long> cids = categoryTerm.getBuckets()
                .stream()
                .map(Terms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        //通过分类id的集合获取分类的对象的集合
        List<Category> categories = itemClient.findCategoryByIds(cids);
        //把结果放入到map中
        resultMap.put("分类", categories);
        return cids;
    }

    //封装查询条件
    private QueryBuilder createQueryBuilder(SearchRequest request) {
        //封装一个Bool对象
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //设置必须的搜索条件
        boolQuery.must(QueryBuilders.multiMatchQuery(request.getKey(), "all", "spuName").operator(Operator.AND));
        //先获取所有的过滤条件
        Map<String, Object> filterParams = request.getFilterParams();
        //遍历过滤条件
        filterParams.entrySet().forEach(entry -> {
            //获取当前entry的key
            String key = entry.getKey();
            //获取当前entry的value
            Object value = entry.getValue();
            //对key值进行处理
            if (StringUtils.equals(key, "分类")) {
                key = "categoryId";
            } else if (!StringUtils.equals(key, "brandId")) {
                key = "specs." + key + ".keyword";
            }
            //设置过滤条件
            boolQuery.filter(QueryBuilders.termQuery(key, value));
        });
        return boolQuery;
    }

    //修改page时监听触发的方法
    public List<GoodsDTO> changePage(SearchRequest request) {
        PageResult<GoodsDTO> pageResult = itemPageQuery(request);
        return pageResult.getItems();
    }

    public void addIndex(Long id) {
        SpuDTO spuDTO = itemClient.findSpuDTOBySpuId(id);
        Goods goods = buildGoods(spuDTO);
        searchRepository.save(goods);
    }

    public void delIndex(Long id) {
        searchRepository.deleteById(id);
    }


}